32. Spring4 - bean 作用域和生命周期, 外部属性文件复制, 自动装配

bean的作用域

在 Spring 中,可以在<bean>元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单实例的还是多实例的。默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的 getBean() 调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton,它是所有 bean 的默认作用域。

当bean的作用域为单例时,Spring 会在IOC 容器对象创建时就创建 bean 的对象实例,也就是说会在 new ClassPathXmlApplicationContext 的时候就创建。而当 bean 的作用域为prototype 时,IOC 容器在获取 bean 的实例时创建 bean 的实例对象,也就是会在调用 getBean 方法 applicationContext.getBean("student", Student.class); 时才会创建。

bean 的生命周期

  1. 通过构造器或工厂方法创建 bean 实例
  2. 为 bean 的属性设置值和对其他 bean 的引用 (依赖注入)
  3. 调用 bean 的初始化方法
  4. bean 可以使用了
  5. 当容器关闭时,调用 bean 的销毁方法

测试生命周期

创建 Person 类,在其中自定义 init 和 destory 方法。并在合适的位置进行内容打印输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.itguigu.ioc.life;

public class Person {
private Integer id;
private String sex;
private String name;

public Integer getId() {
return id;
}
public void setId(Integer id) {
System.out.println("bean的生命周期-2: 为bean的属性设置值");
this.id = id;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(Integer id, String sex, String name) {
super();
this.id = id;
this.sex = sex;
this.name = name;
}
public Person() {
super();
System.out.println("bean的生命周期-1: 通过构造器或工厂方法创建 bean 实例");
}

@Override
public String toString() {
System.out.println("bean的生命周期-4: 使用 bean");
return "Person [id=" + id + ", sex=" + sex + ", name=" + name + "]";
}

public void init() {
System.out.println("bean的生命周期-3: 初始化方法");
}

public void destory() {
System.out.println("bean的生命周期-5: 调用bean的销毁方法");
}
}

创建 life.xml, 进行配置,并指定 init-method 方法和 destroy-method 方法

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 为bean指定初始化和销毁方法 -->
<bean id="person" class="com.itguigu.ioc.life.Person" init-method="init" destroy-method="destory">
<property name="id" value="10001"></property>
<property name="sex" value="男"></property>
<property name="name" value="张三"></property>
</bean>
</beans>

运行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itguigu.ioc.life;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("life.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person); // 打印对象,调用 toString,表示对象可以用了
applicationContext.close(); // close 才会调用 bean 的销毁方法, ApplicationContext 没有 close 方法,所有使用 ClassPathXmlApplicationContext

/**
* bean的生命周期-1: 通过构造器或工厂方法创建 bean 实例
bean的生命周期-2: 为bean的属性设置值
bean的生命周期-3: 初始化方法
bean的生命周期-4: 使用 bean
Person [id=10001, sex=男, name=张三]
bean的生命周期-5: 调用bean的销毁方法
*/
}
}

bean 的后置处理器

bean后置处理器允许在调用初始化方法前后对bean进行额外的处理。也就是说如果加上 bean 的后置处理器,那么 bean 的生命周期由以前的五步变成七步

  1. 通过构造器或工厂方法创建 bean 实例
  2. 为bean的属性设置值和对其他bean的引用 (依赖注入)
  3. 将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
  4. 调用bean的初始化方法
  5. 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
  6. bean可以使用了
  7. 当容器关闭时调用bean的销毁方法

创建类 AfterHandler 并实现 BeanPostProcessor 接口,让其成为一个后置处理器。并在其中修改相关 bean 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itguigu.ioc.life;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class AfterHandler implements BeanPostProcessor{

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// bean 初始化之前
Person person = (Person) bean;
person.setName("这是我在后置处理器中修改的名字");
return person;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// // bean 初始化之后
return bean;
}
}

在 xml 中引用这个后置处理器

1
2
<!-- 应用后置处理器 -->
<bean class="com.itguigu.ioc.life.AfterHandler"> </bean>

再次运行测试,这时就能看到我们在后置处理器中对 Person 对象修改的结果了,name 变成了 “这是我在后置处理器中修改的名字”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itguigu.ioc.life;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("life.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person); // 打印对象,调用 toString,表示对象可以用了
applicationContext.close(); // close 才会调用 bean 的销毁方法, ApplicationContext 没有 close 方法,所有使用 ClassPathXmlApplicationContext

/**
* bean的生命周期-1: 通过构造器或工厂方法创建 bean 实例
bean的生命周期-2: 为bean的属性设置值
bean的生命周期-3: 初始化方法
bean的生命周期-4: 使用 bean
Person [id=10001, sex=男, name=这是我在后置处理器中修改的名字]
bean的生命周期-5: 调用bean的销毁方法
*/
}
}

使用外部的属性文件

当 bean 的配置信息逐渐增多时,查找和修改一些 bean 的配置信息就变得愈加困难。这时可以将一部分信息提取到 bean 配置文件的外部,以 properties 格式的属性文件保存起来,同时在 bean 的配置文件中引用 properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改 properties 属性文件即可。这种技术多用于连接数据库的基本信息的配置。

不使用外部属性文件

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/bookstore"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itguigu.ioc.datasource;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("datasource.xml");
DruidDataSource dataSource = applicationContext.getBean("datasource", DruidDataSource.class);
System.out.println(dataSource);

DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
}
}

使用外部属性文件

将资源文件放在 config 文件下,命名为 db.properties

1
2
3
4
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/bookstore
jdbc.username = root
jdbc.password = 123456

加载外部资源文件

1
2
3
4
<!-- 加载资源文件方式1 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
1
2
<!-- 加载资源文件方式2 (需要引入 context 标签) -->
<context:property-placeholder location="db.properties"/>

加载资源后就可以使用

1
2
3
4
5
6
7
<!-- 读取加载资源文件中的内容 -->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itguigu.ioc.datasource;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("datasource.xml");
DruidDataSource dataSource = applicationContext.getBean("datasource", DruidDataSource.class);
System.out.println(dataSource);

DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
}
}

自动装配(自动注入)

手动装配:以value或ref的方式明确指定属性值都是手动装配。
自动装配:根据指定的装配规则,不需要明确指定,Spring 自动将匹配的属性值注入 bean 中。(也就是非自面量的属性,即需要使用 ref 的)

自动装配使用 autowire,它可以根据某种策略自动为非字面量属性赋值。autowire 共有 5 种方式,这里介绍 byName 和 byType

创建三个类,分别是 Car,Dept,Emp。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.itguigu.ioc.auto;

/**
* 员工的车
* @author rex
*
*/
public class Car {
private Integer cid;
private String cname;


public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Car [cid=" + cid + ", cname=" + cname + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.itguigu.ioc.auto;

/**
* 员工的部门
* @author rex
*
*/
public class Dept {
private Integer did;
private String dname;


public Integer getDid() {
return did;
}
public void setDid(Integer did) {
this.did = did;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept [did=" + did + ", dname=" + dname + "]";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.itguigu.ioc.auto;

/**
* 员工
* @author rex
*
*/
public class Emp {
private Integer eid;
private String ename;
private Car car;
private Dept dept;

public Integer getEid() {
return eid;
}
public void setEid(Integer eid) {
this.eid = eid;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}

public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp [eid=" + eid + ", ename=" + ename + ", car=" + car + ", dept=" + dept + "]";
}
}

以前的手动装配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 以前的手动装配 -->
<bean id="emp" class="com.itguigu.ioc.auto.Emp">
<property name="eid" value="1001"></property>
<property name="ename" value="zhangsan"></property>
<property name="car" ref="car"></property> <!-- 引用已经有的 bean -->
<property name="dept" ref="dept"></property> <!-- 引用已经有的 bean -->
</bean>

<bean id="car" class="com.itguigu.ioc.auto.Car">
<property name="cid" value="66666"></property>
<property name="cname" value="二手奥拓"></property>
</bean>

<bean id="dept" class="com.itguigu.ioc.auto.Dept">
<property name="did" value="22202"></property>
<property name="dname" value="市场运营"></property>
</bean>

根据名称自动装配

这里演示的是 byName, 即 bean 的 id 和属性名一致就能自动装配,所以下面 property 省略了car 和 dept

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 自动装配 byName,bean id 和属性名一致就行,所以下面省略了car 和 dept -->
<bean id="emp" class="com.itguigu.ioc.auto.Emp" autowire="byName"> <!-- byName自动装配 -->
<property name="eid" value="1001"></property>
<property name="ename" value="zhangsan"></property>
<!-- 因为 bean id 和属性名一致,所以这里省略了car 和 dept -->
</bean>

<bean id="car" class="com.itguigu.ioc.auto.Car"> <!-- bean id 和属性名一致 -->
<property name="cid" value="66666"></property>
<property name="cname" value="二手奥拓"></property>
</bean>

<bean id="dept" class="com.itguigu.ioc.auto.Dept"> <!-- bean id 和属性名一致 -->
<property name="did" value="22202"></property>
<property name="dname" value="市场运营"></property>
</bean>

根据类型自动装配

bean 的类型和属性类型一致就能自动装配。甚至 xml 里面可以用子类给父类赋值,也可以用接口实现类给接口赋值。但是要注意,在 Spring 容器中只能存在一个为之匹配赋值的对象,也就是说想用自动装配,那么 Spring 容器中 Car 和 Dept 都只能有一个。不然会和使用类型获取 bean 的 getBean 方法发生同样的错误,NoUniqueBeanDefinitionException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="emp" class="com.itguigu.ioc.auto.Emp" autowire="byType">
<property name="eid" value="1001"></property>
<property name="ename" value="zhangsan"></property>
</bean>

<bean id="car1" class="com.itguigu.ioc.auto.Car">
<property name="cid" value="66666"></property>
<property name="cname" value="二手奥拓"></property>
</bean>

<bean id="dept1" class="com.itguigu.ioc.auto.Dept">
<property name="did" value="22202"></property>
<property name="dname" value="市场运营"></property>
</bean>

byName 和 byType 选用建议: byName 和 byType都有自己的缺点,一个是依赖名称相同,另一个依赖类型相同,且 autowire 会作用于该 bean 的所有非字面量属性,即限制不了不想让某个非字面量属性自动赋值。所以,相对于使用注解的方式实现的自动装配,在 XML 文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。

代码地址